package zpe.controledeacesso.service;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import zpe.controledeacesso.model.bean.GrupoDeUsuario;
import zpe.controledeacesso.model.bean.NivelAcesso;
import zpe.controledeacesso.model.bean.Permissoes;
import zpe.controledeacesso.model.bean.Transacao;
import zpe.controledeacesso.model.bean.Usuario;
import zpe.controledeacesso.security.Acesso;
import zpe.controledeacesso.util.StringUtils;
import zpe.genericcrud.exception.ApplicationException;
import zpe.genericcrud.service.GenericService;

@Service("loginService")
public class LoginService extends GenericService<Usuario>{

	/**
	 * 
	 */
	private static final long serialVersionUID = 845744635533408748L;
	
	@Autowired private GrupoDeUsuariosService grupoService;	
	
	/**
	 * Autentica o usuário, verificando se os dados fornecidos são compatíveis
	 * com os que estão cadastrados.	
	 *  
	 * @param usuario
	 *            Usuário a ser autenticado.
	 * @return Um objeto da classe Usuario se a autenticação foi realizada com
	 *         sucesso, null caso contrário.
	 */
	public Usuario autenticar( Usuario usuario ) { 
		String senhaMd5Min = "";
		String senhaMd5Mai = "";
		Usuario userBase = findByLogin( usuario );
		
		if( userBase == null )
			throw new ApplicationException( "Usuário não cadastrado. Verifique se o login informado está correto." );
		
		/*Encriptando a senha vinda do cliente*/
		senhaMd5Min = StringUtils.md5( usuario.getSenha().toLowerCase() );
		senhaMd5Mai = StringUtils.md5( usuario.getSenha().toUpperCase() );

		/*Comparando senha do banco com senha do cliente*/
		if ( senhaMd5Min.equals(userBase.getSenha()) ) {
			return userBase;
		}else if( senhaMd5Mai.equals( userBase.getSenha() ) ){
			return userBase;
		}else{
			throw new ApplicationException( "Login e/ou senha inválidos. Verifique o login e a senha e tente novamente." );
		}
	}
	
	@SuppressWarnings("unchecked")
	public Usuario findByLogin( Usuario usuario ){
		DetachedCriteria dc = DetachedCriteria.forClass( Usuario.class );
		dc.add( Restrictions.ilike( "login" , usuario.getLogin() ) );
		
		List<Usuario> users = getDao().getEntityManager().findByCriteria( dc );
		
		if( users.isEmpty() ){
			return null;
		}	
		
		return users.get(0);
	}
	
	/**
	 * Configura todas as permissões do usuário para o presente m�dulo. Preenche a lista de permiss�es dos grupos do usu�rio 
	 * do m�dulo em quest�o e retorna um map com as permiss�es do usu�rio sem repeti��o. Considera que permiss�es de update, delete e
	 * insert de uma transa��o est� habilitada, se para pelos menos um dos grupos do usu�rio no m�dulo est� habilitado.  
	 * @param usu�rio logado no sistema.
	 * @return map com as permiss�es do usu�rio.
	 */
	public Map<String, Permissoes> buscarPermissoes( Usuario usuario ){
		
		//Pega o id do módulo do sistema
		int modulo = Acesso.getModuloSistema();
		
		//grupos do usuário
		List<GrupoDeUsuario> grupos = usuario.getGrupos();
		//grupos do usuário no módulo em questão
		List<GrupoDeUsuario> gruposModulo = new ArrayList<GrupoDeUsuario>();
		//Consulta as permissões de cada um dos grupos do módulo em questão
		for( GrupoDeUsuario g : grupos ){
			if( g.getModulo().getId().equals( modulo ) ){//Verifica se o grupo é do módulo em questão
				List<Permissoes> permissoes = new ArrayList<Permissoes>();
				permissoes = grupoService.getServicePermissoes().consultaPermissoesByGrupo( g.getId() );
				g.setGrupoTransacao( permissoes );

				//Adiciona o grupo do usuário no módulo 
				gruposModulo.add( g );
				
				//Se for grupo master, pega todas as transacoes.
				if( g.getNivelDeAcesso().equals( NivelAcesso.MASTER ) ){
					return mapeiaGeral( modulo, g);
				}				
			}
		}
		return configurarTransacoesExclusivas( gruposModulo );
	}
	
	/**
	 * Dá todas as permissões do módulo para um grupo.
	 * @param modulo
	 * @param g
	 * @return
	 */
	private Map<String, Permissoes> mapeiaGeral( int modulo, GrupoDeUsuario g ) {
		Map<String, Permissoes> map = new HashMap<String, Permissoes>();
		List<Transacao> transacoes = getGrupoService().getServiceTransacao().findByModulo( modulo );

		for( Transacao t : transacoes ){
			Permissoes p = new Permissoes();
			p.setGrupoDeUsuario( g );
			p.setTransacao( t );
			p.setInserir( 'S' );
			p.setEditar( 'S' );
			p.setRemover( 'S' );			
			map.put( t.getUsedLabel().trim(), p );//Coloca a permissão com a chave correspondente.			
		}
		return map;
	}
	
	/**
	 * Retorna um map com as permissões do usuário sem repetição. Considera que permissões de update, delete e
	 * insert de uma transação está habilitada, se para pelos menos um dos grupos do usuário no módulo algum está habilitado.
	 * @param tran
	 * @param gruposModulo
	 * @return
	 */
	private Map<String, Permissoes> configurarTransacoesExclusivas( List<GrupoDeUsuario> gruposModulo ) {
		Map<String, Permissoes> map = new HashMap<String, Permissoes>();

		for( GrupoDeUsuario g : gruposModulo ){
			for( Permissoes p : g.getGrupoTransacao() ){
				String chave = p.getTransacao().getUsedLabel();
				if( map.get( chave ) == null ){//Se ainda não foi colocada a permissão no mapa.
					map.put( chave, p );//Coloca a permissão com a chave correspondente.
				}else{
					//Nas condicionais a seguir, se não há uma das permissões na transação, verifica se a transação p atual tem essa permissão.
					if( p.isAtivoBool() ){
						((Permissoes)map.get( chave )).setInserir( 'S' );
					}
					if( p.isEditableBool() ){
						((Permissoes)map.get( chave )).setEditar( 'S' );
					}
					if( p.isRemoveBool() ){
						((Permissoes)map.get( chave )).setRemover( 'S' );
					}
				}
			}
		}
		return map;
	}
	
	public void alterarSenha( Usuario usuario ){
		usuario.setSenha( StringUtils.md5( usuario.getSenha().toUpperCase() ) );
		usuario.setAtualizouSenha( new String("S") );
		super.update( usuario );
	}
	
	public GrupoDeUsuariosService getGrupoService() {
		return grupoService;
	}

	public void setGrupoService(GrupoDeUsuariosService grupoService) {
		this.grupoService = grupoService;
	}
}
